
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

using Anculus.Core;

using Galaxium.Protocol.Xmpp.Library.Xml;
using Galaxium.Protocol.Xmpp.Library.Core;

namespace Galaxium.Protocol.Xmpp.Library.Extensions
{
	public class BlockList: ICollection<JabberID>, IAttachable
	{
		public event EventHandler BecameReady;
		public event EventHandler<JidEventArgs> EntityAdded;
		public event EventHandler<JidEventArgs> EntityRemoved;

		public bool IsSupported { get; private set; }
		
		public Client Client { get; private set; }
		
		public HashSet<JabberID> _list;
		
		public BlockList ()
		{
		}
		
		public void Attach (Client client)
		{
			if (Client != null)
				throw new InvalidOperationException ("BlockList is already attached.");
			
			Client = client;
			
			Client.ConnectionStateChanged += HandleConnectionStateChanged;
			
			if (Client.ConnectionState == ConnectionState.Connected)
				RetrieveBlockList ();
		}

		void HandleConnectionStateChanged (object sender, ConnectionStateEventArgs e)
		{
			if (e.State == ConnectionState.Connected)
				RetrieveBlockList ();
		}
		
		public void Detach ()
		{
			if (Client == null) return;
			Client.ConnectionStateChanged -= HandleConnectionStateChanged;
			Client.DefineSetQueryHandler (Namespaces.Blocking, null);
			Client = null;
			IsSupported = false;
			_list.Clear ();
		}
		
		private void RetrieveBlockList ()
		{
			var thread = new Thread (() => {
				Client.WaitForDisco ();
				IsSupported = Client.ServerInfo.Supports (Namespaces.Blocking);
				
				if (IsSupported) {
					Client.DefineSetQueryHandler (Namespaces.Blocking, HandlePush);
					
					var query = new Iq (IqType.Get, "blocklist", Namespaces.Blocking);
					try {
						var result = Client.SendQuery (query, -1);
						foreach (var item in result.Query.EachChild ("item"))
							_list.Add ((JabberID) item ["jid"]);
					}
					catch (Exception e) {
						Client.DefineSetQueryHandler (Namespaces.Blocking, null);
						Log.Error (e, "Error while retrieving BlockList.");
						IsSupported = false;
					}
				}
				
				OnBecameReady ();
			});
			thread.IsBackground = true;
			thread.Start ();
		}
		
		private void HandlePush (Iq query)
		{
			var q = query.Query;
			
			if (q.Name == "block") {
				foreach (var item in q.EachChild ("item")) {
					try { OnEntityAdded ((JabberID) item ["jid"]); }
					catch {}
				}
			}
			else if (q.Name == "unblock") {
				if (q.IsEmpty) {
					foreach (var blocked in new List<JabberID> (_list))
						OnEntityRemoved (blocked);
					return;
				}
				
				foreach (var item in q.EachChild ("item")) {
					try { OnEntityRemoved ((JabberID) item ["jid"]); }
					catch {}
				}
			}
		}

		public void Block (params JabberID[] entities)
		{
			Block (entities as IEnumerable<JabberID>);
		}
		
		public void Unblock (params JabberID[] entities)
		{
			Unblock (entities as IEnumerable<JabberID>);
		}
		
		public void Block (IEnumerable<JabberID> entities)
		{
			var query = new Iq (IqType.Set, "block", Namespaces.Blocking);
			foreach (var entity in entities)
				query.Query.AppendChild (new Element (null, "item", null, "jid", entity));
			Client.SendQuery (query, -1);
		}
		
		public void Unblock (IEnumerable<JabberID> entities)
		{
			var query = new Iq (IqType.Set, "unblock", Namespaces.Blocking);
			foreach (var entity in entities)
				query.Query.AppendChild (new Element (null, "item", null, "jid", entity));
			Client.SendQuery (query, -1);
		}
		
		#region ICollection<JabberID> implementation
		public void Add (JabberID item)
		{
			if (_list.Contains (item)) return;
			Block (item);
		}
		
		public void Clear ()
		{
			var query = new Iq (IqType.Set, "unblock", Namespaces.Blocking);
			query.Query.AppendChild (new Element ("item"));
			Client.SendQuery (query, -1);
		}
		
		public bool Contains (JabberID item)
		{
			return _list.Contains (item);
		}
		
		public void CopyTo (JabberID[] array, int index)
		{
			_list.CopyTo (array, index);
		}
		
		public bool Remove (JabberID item)
		{
			if (!_list.Contains (item)) return false;
			Unblock (item);
			return true;
		}

		public int Count {
			get { return _list.Count; }
		}
		
		public bool IsReadOnly {
			get { return false; }
		}
		#endregion

		#region IEnumerable<JabberID> implementation
		public IEnumerator<JabberID> GetEnumerator ()
		{
			return _list.GetEnumerator ();
		}

		#endregion

		#region IEnumerable implementation
		IEnumerator IEnumerable.GetEnumerator ()
		{
			return GetEnumerator ();
		}
		#endregion

		private void OnBecameReady ()
		{
			if (BecameReady != null)
				BecameReady (this, EventArgs.Empty);
		}
		
		private void OnEntityAdded (JabberID uid)
		{
			_list.Add (uid);
			if (EntityAdded != null)
				EntityAdded (this, new JidEventArgs (uid));
		}
		
		private void OnEntityRemoved (JabberID uid)
		{
			_list.Remove (uid);
			if (EntityRemoved != null)
				EntityRemoved (this, new JidEventArgs (uid));
		}
	}
}
